package com.xvyy.tingshu.account.service.impl;


import com.alibaba.fastjson.JSON;
import com.xvyy.tingshu.account.mapper.RechargeInfoMapper;
import com.xvyy.tingshu.account.mapper.UserAccountDetailMapper;
import com.xvyy.tingshu.account.mapper.UserAccountMapper;
import com.xvyy.tingshu.account.service.UserAccountService;
import com.xvyy.tingshu.common.execption.TsException;
import com.xvyy.tingshu.common.result.Result;
import com.xvyy.tingshu.common.result.ResultCodeEnum;
import com.xvyy.tingshu.common.util.AuthContextHolder;
import com.xvyy.tingshu.model.account.RechargeInfo;
import com.xvyy.tingshu.model.account.UserAccount;
import com.xvyy.tingshu.model.account.UserAccountDetail;
import com.xvyy.tingshu.vo.account.AccountLockResultVo;
import com.xvyy.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    @Autowired
    private UserAccountMapper userAccountMapper;
    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RechargeInfoMapper rechargeInfoMapper;


    @Override
    @Transactional(rollbackFor = Exception.class)// 操作两张表，需要加事务注解
    public Result<AccountLockResultVo> checkAndLockAccount(AccountLockVo accountLockVo) {
        // 假设开启了openFeign的重试，一定要保证openFeign幂等性
        // 1. 获取属性值
        Long userId = accountLockVo.getUserId();
        BigDecimal amount = accountLockVo.getAmount();
        String content = accountLockVo.getContent();
        String orderNo = accountLockVo.getOrderNo();
        String userAccountResultKey = "user:account:data:" + orderNo;
        // 2. 检查和锁定余额(setIfAbsent Lock....，这里使用redis: leftPush + range)
        // 2.1 为了解决幂等性（openfeign重试导致的重复发送请求的问题）
        List<String> range = stringRedisTemplate.opsForList().range("feign:retry:msg:container" + orderNo, -1, 0);
        if (range.contains(orderNo)) {
            // 返回重复锁定异常
            Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_REPEAT);
        }
        try {
            Long aLong = stringRedisTemplate.opsForList().leftPush("feign:retry:msg:container" + orderNo, orderNo);
            if (aLong == 1) {
                // 锁定余额
                userAccountMapper.opsCheckAndLockAccount(userId, amount);
                // 3.准备AccountLockResultVo对象
                AccountLockResultVo accountResultLockVo = new AccountLockResultVo();
                accountResultLockVo.setUserId(userId);
                accountResultLockVo.setAmount(amount);
                accountResultLockVo.setContent(content);
                // 4. 放入redis中 解锁方便
                stringRedisTemplate.opsForValue().set(userAccountResultKey, JSON.toJSONString(accountResultLockVo));
                // 5. 保存账户流水
                log(userId, "锁定:" + content, "1202", amount, orderNo);
                return Result.ok(accountResultLockVo);
            } else {
                return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_REPEAT);
            }
        } catch (Exception e) {
            // 如果出现异常，删除redis中的数据，防止重复锁定
            stringRedisTemplate.delete("feign:retry:msg:container" + orderNo);
            throw new TsException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }
    }

    /**
     * 保存账户流水
     *
     * @param userId
     * @param title
     * @param tradeType
     * @param amount
     * @param orderNo
     */
    @Override
    public void log(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
        // 1. 流水对象
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);
        // 2. 保存流水
        int count = userAccountDetailMapper.insert(userAccountDetail);
        log.info("保存账户流水到数据库:{}", count > 0 ? "成功" : "失败");
    }

    @Override
    public BigDecimal getAvailableAmount() {
        Long userId = AuthContextHolder.getUserId();
        UserAccount account = userAccountMapper.selectOne(new LambdaQueryWrapper<UserAccount>().eq(UserAccount::getUserId, userId));
        return account == null ? BigDecimal.ZERO : account.getAvailableAmount();
    }

    @Override
    public RechargeInfo getRechargeInfoByOrder(String orderNo) {
        return rechargeInfoMapper.selectOne(new LambdaQueryWrapper<RechargeInfo>().eq(RechargeInfo::getOrderNo, orderNo));
    }

    @Override
    public IPage<UserAccountDetail> findUserConsumePage(IPage<UserAccountDetail> iPage) {
        return userAccountDetailMapper.findUserConsumePage(iPage, AuthContextHolder.getUserId(), "1204");
    }

    @Override
    public IPage<UserAccountDetail> findUserRechargePage(IPage<UserAccountDetail> iPage, Long userId) {
        return userAccountDetailMapper.findUserConsumePage(iPage, userId, "1201");
    }

}
